using System;
using System.Collections.Generic;
using System.Text;
using System.Collections.Specialized;
using System.Reflection;
using NBusiness.ESharp.Compiler;
using NBusiness.CodeDom.Compiler;
using System.CodeDom.Compiler;
using NBusiness.CodeDom;
using System.IO;
using System.Linq;
using NBusiness.Templates;

namespace NBusiness.ESharp.Compiler.Proxy
{
    /// <summary>
    /// This proxy class must be in it's own assembly otherwise loading the proxy object will
    /// result in loading an assembly that is already loaded. The Types will not match and
    /// you won't be able to read anything. Also you must only return simple types because
    /// returning user defined types will cause the assembly they were defined in to be loaded
    /// a second time.
    /// </summary>
    [Serializable]
    public class ESharpCompilerProxy : MarshalByRefObject
    {
        List<EntityFile> _files = new List<EntityFile>();
        List<EntityBuildError> _errors = new List<EntityBuildError>();

        public void CompileEntity<T>(Entity entity, EntityCompileParameters parameters)
            where T : Attribute
        {
            _files.Clear();

            foreach (EntityTemplate template in entity.As)
            {
                if (template.Type != null)
                {
                    Type templateType = Type.GetType(template.Type.ToString(), false);
                    if (templateType == null)
                    {
                        Assembly a = Assembly.LoadFile(template.Type.Path);
                        templateType = a.GetType(template.Type.FullName);
                    }

                    if (templateType != null && 
                        typeof(ITemplate).IsAssignableFrom(templateType) &&
                        templateType.IsDefined(typeof(T), true))
                    {
                        ITemplate instance = (ITemplate)Activator.CreateInstance(templateType);
                        try
                        {
                            instance.BuildError += new EventHandler<EntityBuildErrorEventArgs>(instance_BuildError);

                            string generated = instance.Compile(entity, parameters);

                            if (!string.IsNullOrEmpty(generated))
                            {
                                _files.Add(new EntityFile
                                {
                                    FileName = templateType.Name.ToLower().Replace("template", ""),
                                    Data = generated
                                });
                            }
                        }
                        catch (Exception ex)
                        {
                            _errors.Add(new EntityBuildError(
                                "ES0000", 
                                ex.Message, 
                                new EntityElement("", 0, 0, 0, 0), 
                                EntityBuildErrorType.Error));
                        }
                        finally
                        {
                            instance.BuildError -= new EventHandler<EntityBuildErrorEventArgs>(instance_BuildError);
                        }
                    }
                }
            }
        }

        void instance_BuildError(object sender, EntityBuildErrorEventArgs e)
        {
            _errors.Add(e.Error);
        }

        public int GetErrorCount()
        {
            return _errors.Count;
        }

        public void GetError(
            int index, 
            out string errorCode, 
            out string message, 
            out EntityBuildErrorType type,
            out string path,
            out int line,
            out int column)
        {
            if (index < 0 || index > _errors.Count)
                throw new ArgumentOutOfRangeException("index");

            EntityBuildError error = _errors[index];
            errorCode = error.ErrorCode;
            message = error.Message;
            type = error.Type;
            path = error.Element.Path;
            line = error.Element.Line;
            column = error.Element.Column;
        }

        public string[] GetFileNames()
        {
            return (from f in _files select f.FileName).ToArray();
        }

        public string GetFileData(string filename)
        {
            EntityFile file = (from f in _files where f.FileName == filename select f).FirstOrDefault();
            if(file!=null)
                return file.Data;

            return null;
        }
    }
}
